package cache

import (
	"container/list"
	"context"
	"sync"
	"time"
)

type RemoveReason uint32

const (
	// Expired means the key is past its LifeWindow.
	Expired = RemoveReason(1)
	// NoSpace means the key is the oldest and the cache size was at its maximum when set was called, or the
	// entry exceeded the maximum shard size.
	NoSpace = RemoveReason(2)
	// Deleted means delete was called and this key was removed as a result.
	Deleted = RemoveReason(3)
)

type cacheShardOptions struct {
	// 将所需的 Options 字段列举在这里
	MaxCount       int
	ExpireInterval time.Duration
	Mode           Mode
	OnEvicted      func(string, interface{}, RemoveReason)
}

type cacheShard struct {
	cache         Cache
	options       *cacheShardOptions
	lock          sync.RWMutex
	lis           *list.List
	items         map[string]*list.Element
	expiredItems  map[string]*list.Element
	itemPool      sync.Pool
	onEvicted     func(key string, val interface{}, reason RemoveReason)
	expiryManager *ExpiryManager
	scheduler     *Scheduler
}

func newCacheShard(cache Cache, opts *cacheShardOptions) *cacheShard {
	cs := &cacheShard{
		lock:         sync.RWMutex{},
		lis:          list.New(),
		items:        make(map[string]*list.Element),
		expiredItems: make(map[string]*list.Element),
		cache:        cache,
		onEvicted:    opts.OnEvicted,
		options:      opts,
	}

	cs.expiryManager = NewExpiryManager(cache)
	cs.scheduler = NewScheduler(opts.ExpireInterval, cs.expiryManager)
	cs.scheduler.Start()

	return cs
}

func (c *cacheShard) foreach(fn func(key string, item *Item)) {
	c.lock.RLock() // 读锁
	defer c.lock.RUnlock()

	for k, v := range c.items {
		fn(k, v.Value.(*Item))
	}
}

func (c *cacheShard) foreachExpired(fn func(key string, item *Item)) {
	c.lock.RLock() // 读锁
	defer c.lock.RUnlock()

	for k, v := range c.expiredItems {
		fn(k, v.Value.(*Item))
	}
}

func (c *cacheShard) add(item *Item) {
	ele := c.lis.PushFront(item)
	c.items[item.key] = ele
	if !item.neverExpires {
		c.expiredItems[item.key] = ele
	}
}

func (c *cacheShard) put(ele *list.Element, value interface{}, duration time.Duration) {
	i := ele.Value.(*Item)
	i.value = value
	i.expiryAt = time.Now().Add(duration).Unix()
	if duration > 0 {
		c.expiredItems[i.key] = ele
	} else {
		delete(c.expiredItems, i.key)
		i.neverExpires = true
	}
	c.lis.MoveToFront(ele)
}

func (c *cacheShard) isFull() bool {
	return c.len() == c.options.MaxCount
}

func (c *cacheShard) set(key string, value interface{}, duration time.Duration) error {
	if key == "" {
		return ErrEmptyKey
	}
	if value == nil {
		return ErrNilValue
	}

	c.lock.Lock()
	defer c.lock.Unlock()

	ele, ok := c.items[key]

	if duration < c.scheduler.interval {
		c.scheduler.Start()
	}

	// 体现出抽象的意识，把复杂的多步的逻辑放在一起
	if ok {
		c.put(ele, value, duration)
		return nil
	}

	if c.isFull() {
		c.removeOldest()
	}

	item := newItem(key, value, duration, c.options.Mode)
	c.add(item)

	return nil
}

func (c *cacheShard) removeOldest() {
	if c.lis.Len() == 0 {
		return
	}

	ele := c.lis.Back()
	_ = c.deleteItem(ele.Value.(*Item).key, NoSpace)
}

func (c *cacheShard) get(key string) (interface{}, error) {
	c.lock.Lock() // 读锁
	defer c.lock.Unlock()

	ele, found := c.items[key]
	if !found {
		return nil, ErrItemNotFound
	}
	item := ele.Value.(*Item)

	if item.isExpired() {
		err := c.deleteItem(item.key, Expired)
		if err != nil {
			return nil, err
		}
		return nil, ErrItemExpired
	}

	c.lis.MoveToFront(ele)
	return item.value, nil
}

func (c *cacheShard) delete(key string, reason RemoveReason) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	return c.deleteItem(key, reason)
}

func (c *cacheShard) deleteItem(key string, reason RemoveReason) error {
	ele, ok := c.items[key]
	if ok {
		c.lis.Remove(ele)
		delete(c.items, key)
		delete(c.expiredItems, key)
		if c.onEvicted != nil {
			c.onEvicted(key, ele.Value.(*Item).value, reason)
		}
	} else {
		return ErrItemNotFound // 返回错误信息
	}
	return nil
}

func (c *cacheShard) clear(ctx context.Context) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	c.scheduler.Stop()
	c.items = make(map[string]*list.Element)
	c.expiredItems = make(map[string]*list.Element)
	c.lis = list.New()
	return nil
}

func (c *cacheShard) len() int {
	return len(c.items)
}
